package top.itaso.lib.widget.map;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.core.view.GestureDetectorCompat;


import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import top.itaso.lib.widget.R;
import top.itaso.lib.widget.map.bean.ChinaProvinceItem;

/**
 * @Description: 使用SVG绘制中国地图
 * @Author itsdf07
 * @E-Mail 923255742@qq.com
 * @Github https://github.com/itsdf07
 * @Date 2021/5/30
 */
public class ChinaMapView extends View {
    private final String TAG = this.getClass().getSimpleName();
    private Context context;
    /**
     * 中国地图的南北纵向（约5500km）与东西横向（5200）距离比例
     */
    private static final float CHINAMAP_LENGTHWIDTH_RADIO = 55f / 52f;

    /**
     * 手机设备与提供的SVG地图矫正比例：手机/SVG
     */
    private final float ADJUST_SCALE = 0.93f;
    /**
     * 存放中国所有的省份
     */
    private List<ChinaProvinceItem> provinceItems = new ArrayList<>();

    /**
     * 绘制地图填充的颜色值
     */
    private int[] mapColors = new int[]{Color.YELLOW, Color.RED, Color.BLUE, Color.GREEN, Color.GRAY, Color.LTGRAY};
    /**
     * 绘制地图的画笔
     */
    private Paint mapPaint;

    /**
     * 放大的倍数
     */
    private float scale = ADJUST_SCALE;

    /**
     * 被选中的省份
     */
    private ChinaProvinceItem selectItem;

    /**
     * 对自定义View进行设置最大宽高值
     */
    private int minWidth;
    private int minHeight;
    /**
     * 地图数据是否已经加载完成
     */
    private boolean isMapDataReady = false;

    public ChinaMapView(Context context) {
        this(context, null);
    }

    public ChinaMapView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ChinaMapView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        isMapDataReady = false;
        //初始化自定义View的最小宽高值，将dp转换成px
        if (isScreenOriatationPortrait(context)) {//竖屏时，地图默认最小宽度值为屏幕宽度的一半并根据chinaMapLengthWidthRadio比例算出高度值
            minWidth = getScreenParams()[0] / 2;
            minHeight = (int) ((float) minWidth * CHINAMAP_LENGTHWIDTH_RADIO);
        } else {//横屏时，地图默认高度为屏幕高度的一半并根据chinaMapLengthWidthRadio比例算出宽度值
            minHeight = getScreenParams()[1] / 2;
            minWidth = (int) ((float) minWidth / CHINAMAP_LENGTHWIDTH_RADIO);
        }

        //关闭硬件加速，否则选中的provinceItem的底部阴影图层无法正常绘制，即无效。硬件加速问题，可参考：https://blog.csdn.net/u013598111/article/details/49975843
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mapPaint = new Paint();
        //对Paint进行抗锯齿的设置
        mapPaint.setAntiAlias(true);
        //开始解析/加载SVG格式的地图数据
        loadSvgMapDataThread.start();
    }

    public void setScale(float scale) {
        this.scale += scale;
        invalidate();
    }

    /**
     * 得到屏幕宽高
     *
     * @return 返回px单位的屏幕 宽x高
     */
    private int[] getScreenParams() {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        int[] screenParam = new int[2];
        screenParam[0] = displayMetrics.widthPixels;
        screenParam[1] = displayMetrics.heightPixels;
        return screenParam;
    }

    /**
     * 返回当前屏幕是否为竖屏。
     *
     * @param context
     * @return 当且仅当当前屏幕为竖屏时返回true, 否则返回false。
     */
    private boolean isScreenOriatationPortrait(Context context) {
        return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
    }

    /**
     * 屏幕触屏事件处理
     *
     * @param x
     * @param y
     */
    private void doOnTouch(float x, float y) {
        if (provinceItems != null) {
            ChinaProvinceItem temp = null;
            for (ChinaProvinceItem provinceItem : provinceItems) {
                //因为之前对canvas进行放大了scale倍数，那么这边则需要同等进行缩小scale倍数
                if (provinceItem.isTouch((int) (x / scale), (int) (y / scale))) {
                    temp = provinceItem;
                    break;
                }
            }
            if (temp != null) {
                selectItem = temp;
                postInvalidate();
            }
        }
    }


    Thread loadSvgMapDataThread = new Thread() {
        @Override
        public void run() {
            //读取已经通过工具将美工提供的SVG地图数据转成Android对应的xml数据的数据流（Android SVG to VectorDrawable）
            InputStream inputStream = context.getResources().openRawResource(R.raw.china);
            //得到 DOM 解析器的工厂实例
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            try {
                //从 DOM 工厂获得 DOM 解析器
                DocumentBuilder builder = factory.newDocumentBuilder();
                //解析 XML 文档的输入流，得到一个 Document
                Document doc = builder.parse(inputStream);
                //得到 XML 文档的根节点
                Element rootElement = doc.getDocumentElement();
                //得到path节点的所有子节点，如android:fillColor、android:pathData、android:strokeWidth、android:strokeColor等等
                NodeList pathItems = rootElement.getElementsByTagName("path");
                Log.d(TAG, "loadSvgMapDataThread run: >>>>>>中国省份节点数:" + pathItems.getLength());
                for (int i = 0; i < pathItems.getLength(); i++) {
                    Element pathElement = (Element) pathItems.item(i);
                    //取出path节点中android:pathData的节点数据，该数据即为地图边界瞄点数据
                    String pathData = pathElement.getAttribute("android:pathData");
//                    ALog.v("省份的瞄点数据pathData:%s", pathData);
                    //将符合SVG的 瞄点 转换成Path
                    Path path = PathParser.createPathFromPathData(pathData);
                    ChinaProvinceItem provinceItem = new ChinaProvinceItem(path);
                    int flag = i % mapColors.length;
                    provinceItem.setMapDrawColor(mapColors[flag]);
                    provinceItem.setProvinceName(i + "");
                    provinceItems.add(provinceItem);
                }
                isMapDataReady = true;
                postInvalidate();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    /**
     * 手势检测器
     */
    private GestureDetectorCompat gestureDetector = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            doOnTouch(e.getX(), e.getY());
            return true;
        }
    });

    /**
     * MeasureSpec对应的MODE为--->EXACTLY:1073741824,AT_MOST:-2147483648,UNSPECIFIED:0
     * android:layout_width="match_parent"、android:layout_height="match_parent"  -------------EXACTLY、EXACTLY
     * android:layout_width="match_parent"、android:layout_height="wrap_content"  -------------EXACTLY、AT_MOST
     * android:layout_width="wrap_content"、android:layout_height="wrap_content"  -------------AT_MOST、AT_MOST
     * android:layout_width="100dp"、android:layout_height="100dp"  -------------EXACTLY、EXACTLY
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取该View的宽高模式，如 match_parent、 wrap_content或者具体的dp值
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //单位是px
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        int viewWidth = width;
        int viewHeight = height;
//        ALog.v("EXACTLY:%s,AT_MOST:%s,UNSPECIFIED:%s", MeasureSpec.EXACTLY, MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED);
        //widthMode:1073741824,heighthMode:1073741824,viewWidth:600,viewHeight:1000,minWidth:400,minHeight:294
        Log.d(TAG, "onMeasure: >>>>>>widthMode:" + widthMode + ",heighthMode:" + heightMode + ",width:" + width + ",height:" + height + ",minWidth:" + minWidth + ",minHeight:" + minHeight);
        if (isScreenOriatationPortrait(context)) {
            int computeHeight = height;
            switch (widthMode) {
                //精确值模式。父布局决定了子view的准确尺寸。子view无论想设置多大的值，都将限定在那个边界内，
                // 即xml布局中宽度是具体的dp值或者xml布局中宽度是match_parent或者具体值（如100dp）
                case MeasureSpec.EXACTLY:
                    //这边限制地图最大必须全部显示在手机屏幕内，所以竖屏时当android:layout_width设置了具体值且该值大于屏幕宽度，则取屏幕值，反之使用具体设置的值
                    viewWidth = Math.min(width, getScreenParams()[0]);
                    //根据预置的地图的纵横合理比例值与当前竖屏时屏幕宽度计算出理想宽纵横比例值中的高度值
                    computeHeight = (int) ((float) viewWidth * CHINAMAP_LENGTHWIDTH_RADIO);
                    //同理viewWidth的要求
                    viewHeight = Math.min(computeHeight, getScreenParams()[1]);
                    break;
                //wrap_content
                case MeasureSpec.AT_MOST://最大值模式。子view可以一直大到指定的值。
                    viewWidth = width;
                    //根据预置的地图的纵横合理比例值与当前竖屏时屏幕宽度计算出理想宽纵横比例值中的高度值
                    computeHeight = (int) ((float) viewWidth * CHINAMAP_LENGTHWIDTH_RADIO);
                    //同理viewWidth的要求
                    viewHeight = Math.min(computeHeight, height);
                    break;
                case MeasureSpec.UNSPECIFIED://未指定尺寸模式：父布局没有对子view强加任何限制。它可以是任意想要的尺寸。
                    viewWidth = minWidth;
                    //根据预置的地图的纵横合理比例值与当前竖屏时屏幕宽度计算出理想宽纵横比例值中的高度值
                    computeHeight = (int) ((float) viewWidth * CHINAMAP_LENGTHWIDTH_RADIO);
                    //同理viewWidth的要求
                    viewHeight = Math.min(computeHeight, minHeight);
                    break;
            }
            scale = (float) viewWidth / (float) getScreenParams()[0] * ADJUST_SCALE;
        } else {
            int computeWidth = width;
            switch (heightMode) {
                case MeasureSpec.EXACTLY://精确值模式。父布局决定了子view的准确尺寸。子view无论想设置多大的值，都将限定在那个边界内，即xml布局中宽度是具体的dp值或者xml布局中宽度是match_parent
                    //这边限制地图最大必须全部显示在手机屏幕内，所以横屏时当android:layout_height设置了具体值且该值大于屏幕高度，则取屏幕值，反之使用具体设置的值
                    viewHeight = Math.min(height, getScreenParams()[1]);
                    //根据预置的地图的纵横合理比例值与当前竖屏时屏幕宽度计算出理想宽纵横比例值中的高度值
                    computeWidth = (int) ((float) viewHeight / CHINAMAP_LENGTHWIDTH_RADIO);
                    //同理viewWidth的要求
                    viewWidth = Math.min(computeWidth, getScreenParams()[0]);
                    break;
                //wrap_content
                case MeasureSpec.AT_MOST://最大值模式。子view可以一直大到指定的值。
                    viewHeight = height;
                    //根据预置的地图的纵横合理比例值与当前竖屏时屏幕宽度计算出理想宽纵横比例值中的高度值
                    computeWidth = (int) ((float) viewHeight / CHINAMAP_LENGTHWIDTH_RADIO);
                    //同理viewWidth的要求
                    viewWidth = Math.min(computeWidth, height);
                    break;
                case MeasureSpec.UNSPECIFIED://未指定尺寸模式：父布局没有对子view强加任何限制。它可以是任意想要的尺寸。
                    viewHeight = minHeight;
                    //根据预置的地图的纵横合理比例值与当前竖屏时屏幕宽度计算出理想宽纵横比例值中的高度值
                    computeWidth = (int) ((float) viewHeight / CHINAMAP_LENGTHWIDTH_RADIO);
                    //同理viewWidth的要求
                    viewWidth = Math.min(computeWidth, minWidth);
                    break;
            }
            scale = (float) viewHeight / (float) getScreenParams()[1] * ADJUST_SCALE;
            //TODO 临时矫正横屏时地图横向展示不完整
            viewWidth += 40;
        }
        Log.v(TAG, "onMeasure: >>>>>>最终测量结果：viewWidth=" + viewWidth + ",viewHeight=" + viewHeight + ",scale=" + scale);
        setMeasuredDimension(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY)
                , MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.v(TAG, "onLayout: >>>>>>changed:" + changed + ",left:" + left + ",top:" + top + ",right:" + right + ",bottom:" + bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.v(TAG, "onDraw: >>>>>>");
        //因为省份数据是在该类中进行初始化的(有一定的耗时),这时候会存在provinceItems可能已经通过子线程赋有一定数据，
        // 那么UI线程在使用provinceItems，子线程在更新provinceItems，则会出现java.util.ConcurrentModificationException
        //故可通过isMapDataReady标记位处理provinceItems数据是否已经准备完成后在刷新UI
        if (isMapDataReady && provinceItems != null) {
            canvas.save();
            canvas.scale(scale, scale);
            for (ChinaProvinceItem provinceItem : provinceItems) {
                if (provinceItem != selectItem) {
                    provinceItem.draw(canvas, mapPaint, false);
                }
            }
            if (selectItem != null) {
                selectItem.draw(canvas, mapPaint, true);
            }

        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将onTouch事件转交给gestureDetectorCompat处理
        return gestureDetector.onTouchEvent(event);
    }
}
